<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="amcharts-index.js"></script>
<script type="text/javascript" src="amcharts-xy.js"></script>
<script type="text/javascript" src="amcharts-Animated.js"></script>
<script type="text/javascript" src="llvm-watch-logdata.js"></script>
<style> #llvm_watch { width: 100%; height: 100vh; }</style></head>
<body>
  <div id="llvm_watch"></div>
  <script>
    /* amcharts does not support nested JSON key for data fields */
    const flattenJSON = (obj = {}, res = {}, extraKey = '') => {
	for(key in obj) {
	    if(typeof obj[key] !== 'object') {
		res[extraKey + key] = obj[key];
	    }
	    else {
		flattenJSON(obj[key], res, `${extraKey}${key}.`);
	    }
	}
	return res;
    };

    try {
	/* Create root, set UTC time and animation theme */
	var root = am5.Root.new("llvm_watch");
	root.utc = true;
	root.setThemes([am5themes_Animated.new(root)]);

	/* Create chart */
	var chart = root.container.children.push(
	    am5xy.XYChart.new(root, {
		panX: false,
		panY: false,
		wheelX: "panX",
		scrollbarX: am5.Scrollbar.new(root, { orientation: "horizontal" })
	    })
	);

	/* Create legend */
	var legend = chart.children.push(
	    am5.Legend.new(root, {
		x: am5.percent(50),
		centerX: am5.percent(50),
		y: am5.percent(0),
		centerY: am5.percent(50),
		dy: -5,
		layout: root.horizontalLayout,
		useDefaultMarker: true
	    })
	);

	/* Create X axis */
	var xAxis = chart.xAxes.push(
	    am5xy.DateAxis.new(root, {
		renderer: am5xy.AxisRendererX.new(root, {}),
		baseInterval: { timeUnit: "second", count: 1 }
	    })
	);

	/* Set X axis (vertical) grid width (0 to remove) */
	xAxis.get("renderer").grid.template.setAll({
      	    location: 0,
      	    strokeWidth: 0,
      	    visible:false
	});

	/* Set X axis label rotation */
	xAxis.get("renderer").labels.template.setAll({
            rotation: -45,
            centerY: am5.p50,
            centerX: am5.p100,
	    paddingTop: 10,
	    paddingRight: -5
	});

	/* Set X axis label date format */
	xAxis.get("dateFormats")["day"] = "MMM d, yyyy";
	xAxis.get("periodChangeDateFormats")["day"] = "MMM d, yyyy";

	/* Create Y axis */
	var yAxis = chart.yAxes.push( 
	    am5xy.ValueAxis.new(root, { 
		renderer: am5xy.AxisRendererY.new(root, {}) 
	    }) 
	);

	/* Set Y axis title */
	yAxis.children.unshift(
  	    am5.Label.new(root, {
		rotation: -90,
		text: "Total Code Change (lines)",
		y: am5.p50,
		centerX: am5.p50
  	    })
	);

	/* Create cursor, required for tooltip */
	var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
  	    behavior: "zoomX"
	}));
	cursor.lineX.set("visible", false);
	cursor.lineY.set("visible", false);

	/* Function that returns an anonymous function for creating bullets */
	var bullet = function(bulletColor, bulletOpacity, bulletRadius,
			      tooltipTextColor, tooltipBackgroundColor, tooltipOpacity,
			      tooltipHoverable, tooltipLink) {
	    return function() {
		var tooltip = am5.Tooltip.new(root, {
                    getFillFromSprite: false,
                    autoTextColor: false,
		    keepTargetHover: tooltipHoverable
		});
		tooltip.label.setAll({
		    fill: am5.color(tooltipTextColor)
		});
		tooltip.get("background").setAll({
                    fill: am5.color(tooltipBackgroundColor),
                    fillOpacity: tooltipOpacity
		});

		return am5.Bullet.new(root, {
                    locationY: 1,
                    sprite: am5.Circle.new(root, {
			radius: bulletRadius,
			fill: am5.color(bulletColor),
			fillOpacity: bulletOpacity,
			tooltipHTML: "<font face=\"courier\" color=\"" +
			    tooltipTextColor + "\">" +
			    (tooltipLink ? "<a href=\"" + tooltipLink + "\">" : "") +
			    "{sha1}" +
			    (tooltipLink ? "</a>" : "") + "<br>" +
			    "{date.formatDate('i')}<br>" +
			    "Total:{stat.total} " +
			    "Additions:{stat.additions} " +
			    "Deletions:{stat.deletions}</font><br><br>" +
			    "<div style=\"max-width:" + chart.width()/3 +
			    "px;white-space:pre-wrap;\">{mesg}</div>",
			tooltip: tooltip
                    })
		});
            };
	};

	/* Function to create series */
	var series = function(jsonData, valueXField, valueYField, dateFormat,
			      seriesFillColor, seriesStrokeColor, columnWidth,
                              bulletColor, bulletOpacity, bulletRadius,
			      tooltipTextColor, tooltipBackgroundColor, tooltipOpacity,
			      tooltipHoverable, tooltipLink, legendLabelText) {

	    /* Create column series for the bar chart */
	    var s = chart.series.push(
		am5xy.ColumnSeries.new(root, {
		    xAxis: xAxis,
		    yAxis: yAxis,
		    valueXField: valueXField,
		    valueYField: valueYField,
		    fill: am5.color(seriesFillColor),
		    stroke: am5.color(seriesStrokeColor),
		    adjustBulletPosition: false,
		    legendLabelText: legendLabelText
		})
	    );

	    /* Set width of bar */
	    s.columns.template.setAll({
		width: columnWidth
	    });

	    /* Create bullets for the bar chart and tooltip on the bullet */
	    s.bullets.push(bullet(bulletColor, bulletOpacity, bulletRadius,
				  tooltipTextColor, tooltipBackgroundColor, tooltipOpacity,
				  tooltipHoverable, tooltipLink));

	    /* Create data processor for converting ISO date to timestamp */
	    s.data.processor = am5.DataProcessor.new(root, {
		dateFields: [ valueXField ],
		dateFormat: dateFormat,
	    });

	    /* Process and set the data for the series */
	    s.data.setAll(jsonData);
	    s.appear(1000);

	    /* Disable default rectangle legend marker and create
	     * custom bullet legend marker.
	     */
	    if (legendLabelText) {
		legend.data.push(s);
		var legendDataItem = legend.dataItems[legend.dataItems.length - 1];
		var marker = legendDataItem.get("marker");
		legendDataItem.get("markerRectangle").setAll({
		    forceHidden: true
		});
		marker.children.push(
		    am5.Circle.new(root, {
                        radius: bulletRadius,
                        fill: am5.color(bulletColor),
                        fillOpacity: bulletOpacity,
			dy: 8
		    })
		);
	    }

	    return s;
	};

        /* Split the Jenkins report URL then join again its [0,6) slice to
         * throw away the end of the URL to construct Jenkins job URL. We
         * need to do this since the URL path element jenkin{s|p|x} varies
         * on different archs.
         */
        var jenkins_report_url = window.location.href.split("/");
        var jenkins_job_url = jenkins_report_url.slice(0,6).join("/") + "/";

	/* Data for failed llvm-project commit */
	var failed = [ flattenJSON(logdata.next_state.recent.failed[0]) ];
	var failed_build = logdata.next_state.recent.failed[1];

	/* Data for succeeded llvm-project commit */
	var succeeded = [ flattenJSON(logdata.next_state.recent.succeeded[0]) ];
	var succeeded_build = logdata.next_state.recent.succeeded[1];

	/* Data for llvm-project commit history, reverse to make date ascending */
	var behind = logdata.next_state.llvm_history.history.reverse();
	for (i = 0; i < behind.length; i++) {
	    if (behind[i].sha1 == failed[0].sha1 ||
		behind[i].sha1 == succeeded[0].sha1) continue;
	    behind[i] = flattenJSON(behind[i]);
	}

	/* Create column series for llvm-project commit history bar chart */
	hist   = series(behind,       /* JSON data                          */
			"date",       /* JSON key for valueXField           */
			"stat.total", /* JSON key for valueYField           */
			"i",          /* date format, ISO                   */
			"#d3d3d3",    /* seriesFillColor, light gray        */
			"#d3d3d3",    /* seriesStrokeColor, light gray      */
			0.1,          /* columnWidth                        */
			"#ff00ff",    /* bulletColor, magenta               */
			0.5,          /* bulletOpacity                      */
			5,            /* bulletRadius                       */
			"#000000",    /* tooltipTextColor, black            */
			"#e5e4e2",    /* tooltipBackgroundColor, platinum   */
			0.9,          /* tooltipOpacity                     */
			false,        /* tooltipHoverable                   */
			"",           /* tooltipLink                        */
			"LLVM commits" /* legendLabelText                   */);

	/* Hide history bullets to make the failed bullet stand out.
	 * History bullets will appear when we zoom in.
	 */
	/*hist.set("minBulletDistance", 1);*/

	/* Create column series for the single succeeded commit bar chart */
	if (succeeded_build) {
	    var succeeded_url = jenkins_job_url + succeeded_build + "/console";
	    green  = series(succeeded,    /* JSON data                          */
			    "date",       /* JSON key for valueXField           */
			    "stat.total", /* JSON key for valueYField           */
			    "i",          /* date format, ISO                   */
			    "#d3d3d3",    /* seriesFillColor, light gray        */
			    "#d3d3d3",    /* seriesStrokeColor, light gray      */
			    0.1,          /* columnWidth                        */
			    "#00ff00",    /* bulletColor, green                 */
			    0.9,          /* bulletOpacity                      */
			    10,           /* bulletRadius                       */
			    "#00ff00",    /* tooltipTextColor, green            */
			    "#e5e4e2",    /* tooltipBackgroundColor, platinum   */
			    0.9,          /* tooltipOpacity                     */
			    true,         /* tooltipHoverable                   */
			    succeeded_url,/* tooltipLink                        */
			   "last successful commit" /*legendLabelText           */);
	}

	/* Create column series for the single failed commit bar chart */
	if (failed_build) {
	    var failed_url = jenkins_job_url + failed_build + "/console";
	    red    = series(failed,       /* JSON data                          */
			    "date",       /* JSON key for valueXField           */
			    "stat.total", /* JSON key for valueYField           */
			    "i",          /* date format, ISO                   */
			    "#d3d3d3",    /* seriesFillColor, light gray        */
			    "#d3d3d3",    /* seriesStrokeColor, light gray      */
			    0.1,          /* columnWidth                        */
			    "#ff0000",    /* bulletColor, red                   */
			    0.9,          /* bulletOpacity                      */
			    10,           /* bulletRadius                       */
			    "#ff0000",    /* tooltipTextColor, red              */
			    "#e5e4e2",    /* tooltipBackgroundColor, platinum   */
			    0.9,          /* tooltipOpacity                     */
			    true,         /* tooltipHoverable                   */
			    failed_url,   /* tooltipLink                        */
			    "first failed commit" /* legendLabelText            */);
	}

	/* Put the usage label */
	chart.children.unshift(am5.Label.new(root, {
	    text: "click+drag  : zoom in\n" +
                  "l/r scroll  : pan chart\n" +
                  "double click: reset zoom\n" +
                  "click legend: toggle pts",
	    fontFamily: "monaco",
            fontSize: 12,
            fontWeight: "normal",
            textAlign: "left",
            position: "relative",
            x: am5.percent(100),
	    centerX: am5.percent(100),
            y: am5.percent(5),
            paddingTop: 0,
            paddingBottom: 0
        }));

	/* Put the "Commits behind" and "commits left"/"converged" label */
	var converged = logdata.next_state.converged;
	var nhistory = logdata.next_state.llvm_history.history.length;
	var ndropped = logdata.next_state.commits_dropped.length;
	var nleft = logdata.next_history.history.length;
	chart.children.unshift(am5.Label.new(root, {
            text: "Commits behind : " + (nhistory + ndropped) +
                  " (" + (converged ? "converged" : "commits left: " + nleft) + ")\n" +
                  "Commits dropped: " + ndropped,
            fontFamily: "Monaco",
            fontSize: 18,
            fontWeight: "normal",
            textAlign: "left",
            position: "relative",
            x: am5.percent(8),
            y: am5.percent(5),
            paddingTop: 0,
            paddingBottom: 0
	}));

	/* Double click to zoom out */
	chart.events.on("dblclick", function(ev) {
	    chart.zoomOut();
	});

	chart.appear(1000, 100);

    } catch(e) {
	console.log(e);
    }
  </script>
</body>
</html>
